using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace DslCrossModelFramework {

    public class AssemblyReferenceHelper : IDisposable {
        private AppDomain tempAppDomain = null;
        private AssemblyReferenceLoader assemblyLoader;

        public AssemblyReferenceHelper() {
            SetupAppDomain();
        }

        private void SetupAppDomain() {
            if(tempAppDomain == null) {
                AppDomainSetup info = new AppDomainSetup();
                info.ShadowCopyFiles = "true"; //boolean as string? weird.
                info.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
                tempAppDomain =
                    AppDomain.CreateDomain(
                        "AssemblyReferenceHelperAppDomain", null, info);
                assemblyLoader = (AssemblyReferenceLoader)tempAppDomain.CreateInstanceAndUnwrap(typeof(AssemblyReferenceLoader).Assembly.FullName, typeof(AssemblyReferenceLoader).FullName);
            }
        }

        public Stream GetManifestResourceStream(string assemblyPath, string resourceName) {
            try {
                return assemblyLoader.GetManifestResourceStream(assemblyPath, resourceName);
            }
            catch (Exception ex) {
                throw new AssemblyReferenceLoadException(assemblyPath, string.Format("GetManifestResourceStream:{0}", resourceName), ex);
            }
        }

        public string GetAssemblyName(string assemblyPath) {
            try {
                return assemblyLoader.GetAssemblyName(assemblyPath);
            }
            catch (Exception e) {
                throw new AssemblyReferenceLoadException(assemblyPath, "GetAssemblyName", e);
            }
        }

        public string GetAssemblyFullName(string assemblyPath) {
            try {
                return assemblyLoader.GetAssemblyFullName(assemblyPath);
            }
            catch (Exception e) {
                throw new AssemblyReferenceLoadException(assemblyPath, "GetAssemblyFullName", e);
            }
        }

        public List<IModelFile> GetModelFiles(string assemblyPath, IEnumerable<DslToolsPackageInfo> packageInfo, string referencingProject) {
            try {
                List<IModelFile> modelFiles = new List<IModelFile>();

                foreach (string resourceName in assemblyLoader.GetManifestResourceNames(assemblyPath)) {
                    foreach (DslToolsPackageInfo package in packageInfo) {
                        if (resourceName.EndsWith(package.FileExtension)) {
                            ReferenceModelFile modelFile =
                                new ReferenceModelFile(GetAssemblyFullName(assemblyPath), assemblyPath, GetAssemblyName(assemblyPath), resourceName, referencingProject, package);
                            modelFiles.Add(modelFile);
                            break;
                        }
                    }
                }

                return modelFiles;
            }
            catch (Exception e) {
                throw new AssemblyReferenceLoadException(assemblyPath, "GetModelFiles", e);
            }
        }

        public void Dispose() {
            UnloadAppDomain();
        }

        public void UnloadAppDomain() {
            assemblyLoader = null;
            if (tempAppDomain != null) {
                AppDomain.Unload(tempAppDomain);
            }
        }
    }

    /// <summary>
    /// Occurs when a assembly reference could not be loaded
    /// </summary>
    public class AssemblyReferenceLoadException : Exception {
        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyReferenceLoadException"/> class.
        /// </summary>
        /// <param name="assemblyPath">The assembly path.</param>
        /// <param name="operation">The operation.</param>
        /// <param name="inner">The inner exception.</param>
        public AssemblyReferenceLoadException(string assemblyPath, string operation, Exception inner)
            : base(
                string.Format("Could not load assembly reference at '{0}' for operation '{1}': {2}", assemblyPath,
                              operation, inner == null ? "" : inner.Message)) { }
    }

    public class AssemblyReferenceLoader : MarshalByRefObject {
        private Dictionary<string, Assembly> assemblies = new Dictionary<string, Assembly>();

        public AssemblyReferenceLoader() {
            AppDomain.CurrentDomain.AssemblyLoad += delegate(object sender, AssemblyLoadEventArgs args) {
                                                        assemblies.Add(args.LoadedAssembly.Location, args.LoadedAssembly);
                                                    };
        }

        public Assembly LoadAssembly(string assemblyPath) {
            try {
                if(!assemblies.ContainsKey(assemblyPath)) {
                    return Assembly.LoadFrom(assemblyPath);    
                }
                else {
                    return assemblies[assemblyPath];
                }
            }
            catch (FileNotFoundException) {
            }
            catch (FileLoadException) {
            }
            return null;
        }

        public string GetAssemblyName(string assemblyPath) {
            Assembly assembly = LoadAssembly(assemblyPath);
            return assembly.GetName().Name;
        }

        public string GetAssemblyFullName(string assemblyPath) {
            Assembly assembly = LoadAssembly(assemblyPath);
            return assembly.FullName;
        }

        public string[] GetManifestResourceNames(string assemblyPath) {
            try {
                Assembly assembly = LoadAssembly(assemblyPath);
                string[] resources = assembly.GetManifestResourceNames();
                return resources;
            }
            catch (FileNotFoundException) {
            }
            catch (FileLoadException) {
            }
            return null;
        }

        public Stream GetManifestResourceStream(string assemblyPath, string resource) {
            Assembly assembly = LoadAssembly(assemblyPath);
            Stream res = assembly.GetManifestResourceStream(resource);
            return res;
        }
    }
}